"use strict";

define(['config', 'url', 'base64', 'ajax'], (config, url, base64, ajax) => {
    return () => {
        var base = config.api;
        if (base == null) return;
        if (base.endsWith("/")) base = base.substr(0, base.length-1);


        //----------------------------------------------------------------------
        // authentication

        var roles = "admin user".split(/\s+/);

        var parse_token = token => {
            // parse
            var shards = token.split('.');
            if (shards.length != 2) return;
            var raw_token = JSON.parse(base64.decode(shards[0]));

            // simple validation
            if (roles.indexOf(raw_token.role) === -1) return;
            if (raw_token.seq == null) return;

            return raw_token;
        };

        // login : string -> string -> (string? `error` -> ()) -> ()
        var login = (username, password, done) => {
            if (done == null) throw 'login expects 3 arguments (username,password,callback)';
            var ok = json => {
                api.token = json.token;
                api.role = parse_token(json.token).role;
                api.username = username;
                api.name = json.name;
                api.phone = json.phone;
                done(null);
            };
            var fail = error => {
                console.error(error);
                done(error);
            };
            ajax.json(true, api.base + "/api/user/login",
                      { username, password }, ok, fail);
        };

        // validate : string -> (bool `valid`-> ()) -> ()
        // shall be called at most once
        // this is used for auto login
        var validate_called = false;
        var validate = (token, done) => {
            if (done == null) throw 'validate expects 2 arguments (token,callback)';
            if (validate_called) throw `invalid call to validate.`;
            validate_called = true;

            if (parse_token(token) == null)
                return done(false);

            var ok = json => {
                api.token = token;
                api.role = parse_token(token).role;
                api.username = json.username;
                api.name = json.name;
                api.phone = json.phone;
                done(true);
            };
            var fail = error => {
                console.error(error);
                done(false);
            };
            ajax.json(false, api.base + "/api/user/validate",
                      { token }, ok, fail);
        };

        var logout = () => {
            delete api.token;
            delete api.role;
            delete api.username;
            delete api.name;
            delete api.phone;
        };

        var api = { base, roles, login, validate, logout };


        //----------------------------------------------------------------------
        // APIs

        var zip_args = (arg_names, args) => {
            var result = {};
            arg_names.forEach((name, i) => {
                var arg = args[i];
                if (arg == null)
                    throw "bad api call: null not allowed in ("
                        + name + ") of (" + arg_names + ")";
                result[name] = arg;
            });
            return result;
        };

        var make_scalar_api_invoker = (post, addr, arg_names) => {
            return (...args) => {
                var remain = args.length - arg_names.length;
                if (remain !== 1 && remain !== 2)
                    throw "bad api call: expect " + (arg_names.length+1) + " or "
                        + (arg_names.length+2)
                        + " arguments (" + arg_names + ",callback,[update]), but "
                        + args.length + " were given.";

                var callback = args[arg_names.length];
                if (typeof callback !== 'function') throw "bad api call: callback must be a function";
                var update = args[arg_names.length+1];
                if (update == null) update = () => {};
                if (typeof update !== 'function') throw "bad api call: optional 'update' must be a function";

                var ok = () => callback(null);
                var fail = error => {
                    console.error(error);
                    if (error === 'denied')
                        return callback("没有权限");
                    if (error === 'bad argument: not unique')
                        return callback("已存在");
                    if (error.endsWith('outdated')) {
                        update();
                        return callback("您来晚了一步。");
                    }
                    if (error.startsWith('bad argument:'))
                        return callback("参数错误：" + error);
                    callback("服务器错误");
                };

                var zargs = zip_args(arg_names, args);
                zargs.token = api.token;
                ajax.json(post, addr, zargs, ok, fail);
            };
        };

        var make_list_api_invoker = (addr, arg_names) => {
            return (...args) => {
                var zargs = zip_args(arg_names, args);
                zargs.token = api.token;

                return () => {
                    var after = -1;
                    var end = false;

                    return callback => {
                        if (typeof callback !== 'function')
                            throw "callback must be a function";

                        var ok = list => {
                            if (list.length === 0) end = true;
                            else after = list[list.length-1].id;
                            callback(list);
                        };
                        var fail = error => {
                            console.error(error);
                            callback(null);
                        };

                        if (end) callback([]);
                        else {
                            zargs.after = after;
                            ajax.get_json(addr, zargs, ok, fail);
                        }
                    };
                };
            };
        };

        var make_api_invoker = spec => {
            var arg_names = spec.split(/\s+/);
            var addr = arg_names.shift();

            var invoker;
            if (addr.startsWith('@')) {
                addr = addr.substr(1);
                invoker = make_list_api_invoker(base + "/api" + addr, arg_names);
            }
            else {
                var post = false;
                if (addr.startsWith('!')) {
                    post = true;
                    addr = addr.substr(1);
                }
                invoker = make_scalar_api_invoker(post, base + "/api" + addr, arg_names);
            }

            var name = addr.substr(1).split('/').join('_');
            return { name, invoker };
        };

        var populate_api = specs =>
            specs.map(make_api_invoker)
                .forEach(x => api[x.name] = x.invoker);

        populate_api([
            // 用户
            "!/user/add username password name phone",
            "@/user/list search",
            "!/user/password user new",
            "!/user/password/self new",
            "/user/name/self new",
            "/user/phone/self new",

            // 快讯
            "@/news/list search",
            "!/news/add body",
            "!/news/body news new",
            "/news/remove news",

            // 车辆
            "@/auto/list/available search",
            "@/auto/list search",
            "!/auto/add name description price",
            "/auto/remove auto",

            // 订单
            "@/order/list search",
            "@/order/list/self search",
            "!/order/add auto",
            "/order/cancel/self order",
            "/order/cancel order",
            "/order/deliver order",
            "/order/return order",

            // 反馈
            "@/feedback/list search",
            "@/feedback/list/self search",
            "!/feedback/add body",
            "/feedback/remove/self feedback",
            "!/feedback/reply feedback reply",
        ]);

        return api;
    };
});

